////////////////////////////////////////////////////////////////////////
// FILE:        argparser.h
// AUTHOR:      Johannes Winkelmann, jw@tks6.net
// COPYRIGHT:   (c) 2004 by Johannes Winkelmann
// ---------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
////////////////////////////////////////////////////////////////////////

#ifndef _ARGPARSER_H_
#define _ARGPARSER_H_

#include <map>
#include <vector>
#include <string>

// TODO:
// -- important
// - allow multiple occurences of arguments:
//   prt-get --config-append="..." --config-append="..."
// - Allow global --help, --usage
// - check for duplicate entries

// -- nice to have
// 2. allow optional values for args, like --with-gtk[=DIR]
// 4. improve predefined commands: allow descriptions even for such
// 7. Add predefined --version, show in usage; add Contact text
// 8. Allow disabling of predefined options
// 9. make parseError more convenient (passing cmd->name all over...)

class APOpt;
class APCmd;


/**
 * \brief argument parser class
 *
 * Yet another argument parser, meant to speed up development of new
 * applications. Its focus is on being object oriented and safe to use
 */
class ArgParser
{
public:
    /**
     * types of argument number checking
     */
    enum ArgNumberCheck { NONE, MIN, EQ, MAX };

    /*
     * APOpt and APCmd are the classes used in client code, to make
     * efficient comparison of the selected command.
     *
     * In addition, it's currently mainly for programs which use a
     * command syntax, much like CVS
     */
    class APOpt
    {
    public:
        friend class ArgParser;
        APOpt() : id(-1), m_initialized(false) {}
        bool operator ==(const APOpt& other) const { return other.id == id; }

        void init(const std::string& longName,
                  char shortName,
                  const std::string& description,
                  const bool valueRequired=false,
                  const std::string& valueName="") {

            m_initialized = true;
            m_longName = longName;
            m_shortName = shortName;
            m_description = description;
            m_valueRequired = valueRequired;
            m_valueName = valueName;
        }

    private:
        int id;
        std::string m_longName;
        char m_shortName;
        std::string m_description;
        bool m_valueRequired;
        std::string m_valueName;

        bool m_initialized;
    };

    class APCmd
    {
    public:
        friend class ArgParser;
        APCmd() : id(-1) {}
        bool operator ==(const APCmd& other) const { return other.id == id; }

    private:
        int id;
    };

private:

    // internal representation of options and commands
    class Option
    {
    public:
        int id;
        std::string description;

        char shortName;
        std::string longName;


        bool requiresValue;
        std::string valueName;
    };

    class Command
    {
    public:
        int id;
        std::string name;
        std::string description;
        ArgNumberCheck argNumberCheck;
        unsigned int argNumber;
        std::string otherArguments;

        std::map<int, Option*> mandatoryOptions;
        std::map<int, Option*> options;

        ArgParser::APCmd* apCmd;
    };



public:
    ArgParser();
    virtual ~ArgParser();


    /**
     * add a command
     *
     * \param cmd a reference to the command; use it to compare the actually selected command against this one after parsing
     * \param name the name of the command to be parsed from the command line
     * \param description a description, used for the help screens
     * \param argNumberCheck what kind of argument number checking
     * \param argNumber optional number of arguments
     * \param otherOptions value to display in the help screen for following (non option) arguments
     */
    int addCommand(APCmd& cmd,
                   const std::string& name,
                   const std::string& description,
                   ArgNumberCheck argNumberCheck,
                   const int argNumber=-1,
                   const std::string& otherArguments="");


    /**
     * add an option to a command - this will fail with an assertion
     * of \a key has not been initialized using init()
     *
     * \param cmd the command to add an option to
     * \param key the option reference; use it to check for certain options after parsing
     * \param required whether this option is required
     * \param longName the long name of this command (to be used with '--'); leave it empty if you don't want to use a long option name
     * \param shortName the short name of this command (to be used with '-'); pass 0 if you don't want to use a short option name
     * \param description the description of this option, to be used in the help screen
     * \param valueRequired whether this option requires a value
     * \param valueName the name of the value, to be used in the help screen
     */
    int addOption(const APCmd& cmd,
                  APOpt& key,
                  bool required);

    /**
     * the actual parsing. Highly recommended :-)
     */
    void parse(int argc, char** argv);

    /**
     * the command which was parsed, to be used to compare against
     * actual APCmd obtained from addCommand calls
     */
    APCmd command() const;

    /**
     * the name of the application, from argv[0]
     */
    std::string appName() const;

    /**
     * \return true if \a key is set, false otherwise
     */
    bool isSet(const APOpt& key) const;

    /**
     * \return the value attached to the option \key if any
     */
    std::string getOptionValue(const APOpt& key) const;

    /**
     * the remaining arguments
     */
    const std::vector<std::string>& otherArguments() const;

    /**
     * \return an application identification to be used in the usage
     */
    virtual std::string getAppIdentification() const { return ""; }

private:

    std::string generateHelpForCommand(const std::string& command) const;
    std::string generateUsage() const;

    bool isSet(int key) const;

    std::string generateOptionString(Option* o) const;

    void parseError(const std::string& error, const std::string& cmd="") const;

    std::map<std::string, Command*> m_commands;
    std::map<int, Command*> m_commandIdMap;
    std::map<int, Option*> m_options;

    std::map<char, Option*> m_optionsByShortName;
    std::map<std::string, Option*> m_optionsByLongName;
    std::map<int, std::string> m_setOptions;

    std::vector<std::string> m_otherArguments;
    APCmd m_command;
    std::string m_appName;


    int m_cmdIdCounter;
    int m_optIdCounter;
    APOpt PREDEFINED_CMD_HELP;
};


#endif /* _ARGPARSER_H_ */
